
#include <unordered_map>
#include <sstream>
#include "base/ZipUtils.h"
#include "base/base64.h"
#include "base/CCDirector.h"
#include "platform/CCFileUtils.h"
#include "TiledParser.h"
#include "TiledMap.h"
#include "math/Vec2.h"
#include "tinyxml2/tinyxml2.h"
#include "TiledDefs.h"
#include "renderer/CCTexture2D.h"
#include "renderer/CCTextureCache.h"
#include "Path.h"
#include "2d/CCSpriteFrame.h"


using namespace std;

namespace Tiled {
// implementation TMXLayerInfo
TmxLayerInfo::TmxLayerInfo()
: tiles(nullptr)
, offset(Vec2::ZERO)
{
}

TmxLayerInfo::~TmxLayerInfo()
{
    CCLOGINFO("deallocing TMXLayerInfo: %p", this);
    if(tiles)
    {
        free(tiles);
        tiles = nullptr;
    }
}

bool TmxLayerInfo::load(const tinyxml2::XMLElement* element)
{
	LayerData::load(element);

	if (element->Attribute("x"))
		offset.x = element->IntAttribute("x");
	if (element->Attribute("y"))
		offset.y = element->IntAttribute("y");

	size.width = element->IntAttribute("width");
	size.height = element->IntAttribute("height");

	const tinyxml2::XMLElement* dataElem = element->FirstChildElement("data");
	if (dataElem->Attribute("encoding"))
	{
		std::string encoding = dataElem->Attribute("encoding");
		std::string compression = dataElem->Attribute("compression");

		if (encoding == "base64")
		{
			CCASSERT(compression == "" || compression == "gzip" || compression == "zlib", "TMX: unsupported compression method");

			unsigned char *buffer;
			int len = 0;
			std::string datastr = dataElem->GetText();
			len = base64Decode((const unsigned char*)datastr.c_str(), (unsigned int)datastr.length(), &buffer);
			if (!buffer)
			{
				CCLOG("cocos2d: TiledMap: decode data error");
				return false;
			}

			if (compression == "gzip" || compression == "zlib")
			{
				unsigned char *deflated = nullptr;
				ssize_t sizeHint = size.width * size.height * sizeof(unsigned int);

				ssize_t CC_UNUSED inflatedLen = ZipUtils::inflateMemoryWithHint(buffer, len, &deflated, sizeHint);
				CCASSERT(inflatedLen == sizeHint, "");

				free(buffer);
				buffer = nullptr;

				if (!deflated)
				{
					CCLOG("cocos2d: TiledMap: inflate data error");
					return false;
				}

				tiles = reinterpret_cast<uint32_t*>(deflated);
			}
			else
			{
				tiles = reinterpret_cast<uint32_t*>(buffer);
			}
		}
	}
	else
	{
		int tilesAmount = size.width * size.height;
		tiles = (uint32_t*)calloc(tilesAmount, sizeof(uint32_t));
	}

	return true;
}

bool TmxMapInfo::init(const char* tmxString, size_t size, const char* resourcePath)
{
	_path = resourcePath;
	return parseXMLString(tmxString, size);
}

TmxMapInfo::TmxMapInfo()
: _mapSize(Size::ZERO)    
, _tileSize(Size::ZERO)
{
	_objectGroups.reserve(4);
}

TmxMapInfo::~TmxMapInfo()
{
    CCLOGINFO("deallocing TMXMapInfo: %p", this);
}

void TmxMapInfo::loadTileSetXml(const tinyxml2::XMLElement* element, int firstgid)
{
	if (element->FirstChildElement("image"))
	{
		int tileWidth = element->IntAttribute("tilewidth");
		int tileHeight = element->IntAttribute("tileheight");
		int spacing = element->IntAttribute("spacing");
		int margin = element->IntAttribute("margin");

		auto imageElem = element->FirstChildElement("image");
		
		std::string texpath = Path::join(_path, imageElem->Attribute("source"));
		Texture2D* tex = Director::getInstance()->getTextureCache()->addImage(texpath);

		int imageWidth = imageElem->Attribute("width") ? imageElem->IntAttribute("width") : tex->getContentSize().width;
		int imageHeight = imageElem->Attribute("height") ? imageElem->IntAttribute("height") : tex->getContentSize().height;

		int index = 0;
		for (int y = margin; y + tileHeight <= imageHeight - margin; y += tileHeight + spacing)
		{
			for (int x = margin; x + tileWidth <= imageWidth - margin; x += tileWidth + spacing)
			{
				_gidToTile[firstgid + (index++)] = SpriteFrame::createWithTexture(tex, Rect(x, y, tileWidth, tileHeight));
			}
		}
	}

	for (auto tileElem = element->FirstChildElement("tile"); tileElem; tileElem = tileElem->NextSiblingElement("tile"))
	{
		int gid = firstgid + tileElem->IntAttribute("id");

		for (auto childElement = tileElem->FirstChildElement();
			childElement; childElement = childElement->NextSiblingElement())
		{
			std::string name = childElement->Name();

			if (name == "properties")
			{
				PropertySet propset;
				propset.load(childElement);
				_gidToProperty[gid] = propset;
			}
			else if (name == "image")
			{
				std::string texpath = Path::join(_path, childElement->Attribute("source"));
				int w = childElement->IntAttribute("width");
				int h = childElement->IntAttribute("height");

				_gidToTile[gid] = SpriteFrame::create(texpath, Rect(0, 0, w, h));
			}
		}
	}
}

void TmxMapInfo::loadTileSet(const tinyxml2::XMLElement* element)
{
	if (element->Attribute("source"))
	{
		std::string tileSetPath = Path::join(_path, element->Attribute("source"));
		Data xmlStr = FileUtils::getInstance()->getDataFromFile(tileSetPath);

		tinyxml2::XMLDocument doc;
		doc.Parse((const char*)xmlStr.getBytes(), xmlStr.getSize());
		
		loadTileSetXml(doc.RootElement(), element->IntAttribute("firstgid"));
	}
	else 
	{
		loadTileSetXml(element, element->IntAttribute("firstgid"));
	}

}

bool TmxMapInfo::parseXMLString(const char* data, size_t size)
{
	tinyxml2::XMLDocument tmxDocument;
	tmxDocument.Parse(data, size);

	auto rootElem = tmxDocument.RootElement();

	if (!rootElem)
		return false;
	
	std::string version = rootElem->Attribute("version");
	if (version != "1.0")
	{
		CCLOG("cocos2d: TMXFormat: Unsupported TMX version: %s", version.c_str());
	}
	std::string orientationStr = rootElem->Attribute("orientation");

	if (orientationStr == "orthogonal")
		_orientation = TmxOrientationOrtho;
	else if (orientationStr == "isometric")
		_orientation = TmxOrientationIso;
	else if (orientationStr == "hexagonal")
		_orientation = TmxOrientationHex;
	else if (orientationStr == "staggered")
		_orientation = TmxOrientationStaggered;
	else
		CCLOG("cocos2d: TMXFomat: Unsupported orientation: %d", _orientation);

	_properties.load(rootElem->FirstChildElement("properties"));

	_mapSize.width = rootElem->IntAttribute("width");
	_mapSize.height = rootElem->IntAttribute("height");

	_tileSize.width = rootElem->IntAttribute("tilewidth");
	_tileSize.height = rootElem->IntAttribute("tileheight");

	for (auto childElement = rootElem->FirstChildElement(); 
		childElement; childElement = childElement->NextSiblingElement())
	{
		bool ret = true;
		std::string name = childElement->Name();

		if (name == "tileset")
		{
			loadTileSet(childElement);
		}
		else if (name == "layer")
		{
			loadLayer(childElement);
		}
		else if (name == "objectgroup")
		{
			loadObjectGroup(childElement);
		}
		else if (name == "imagelayer")
		{
	
		}

		if (!ret)
		{
			return false;
		}
	}

	return true;
}

void TmxMapInfo::loadLayer(const tinyxml2::XMLElement* element)
{
	TmxLayerInfo *layer = new (std::nothrow) TmxLayerInfo();
	_layers.pushBack(layer);
	layer->load(element);
}

void TmxMapInfo::loadObject(TmxObjectGroup* group, const tinyxml2::XMLElement* element)
{
	
}

void TmxMapInfo::loadObjectGroup(const tinyxml2::XMLElement* element)
{
// 	TmxObjectGroup *objectGroup = new (std::nothrow) TmxObjectGroup();
// 	objectGroup->setGroupName(element->Attribute("name"));
// 
// 	Vec2 positionOffset;
// 	positionOffset.x = element->IntAttribute("x") * getTileSize().width;
// 	positionOffset.y = element->IntAttribute("y") * getTileSize().height;
// 	objectGroup->setPositionOffset(positionOffset);
// 
// 	for (auto objElem = element->FirstChildElement("object"); objElem; objElem = objElem->NextSiblingElement("object"))
// 	{
// 		loadObject(objectGroup, objElem);
// 	}
}

TmxLayerInfo* TmxMapInfo::getLayer(const std::string& name) const
{
	for (const auto& iter : _layers) {
		if (iter->name == name)
			return &(*iter);
	}
	return nullptr;
}

cocos2d::Vec2 TmxMapInfo::posToTileIndex(float x, float y)
{
	if (_orientation == TmxOrientationIso)
	{//todo
// 		int x_sub_y = (int)(x * 2.0f / _tileSize.width + 1 - _mapSize.width);
// 		int x_add_y = (int)(_mapSize.height * 2.0f - y * 2.0f / _tileSize.height - 2.0f);
// 		return Vec2((x_sub_y - x_add_y) / 2, (x_sub_y - x_add_y) / 2);
	}
	return Vec2(floor(x / _tileSize.width), floor(_mapSize.height - y / _tileSize.height));
}

cocos2d::Vec2 TmxMapInfo::tileIndexToPos(int x, int y)
{
	if (_orientation == TmxOrientationIso)
	{
		return Vec2((_mapSize.width + x - y - 1) * _tileSize.width * 0.5f, (_mapSize.height * 2 - x - y - 2) * _tileSize.height * 0.5f);
	}

	return Vec2(_tileSize.width * x, _tileSize.height * (_mapSize.height - y - 1));
}

void TmxObjectGroup::Load(const tinyxml2::XMLElement* element, const TmxMapInfo& info)
{
	LayerData::load(element);
}

}